/*
 *   Copyright 2008 Francois Fernandes
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package com.googlecode.jobjectlife;

import static com.googlecode.jobjectlife.internal.Assert.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import com.googlecode.jobjectlife.annotations.RequireState;
import com.googlecode.jobjectlife.annotations.TransitionAfter;
import com.googlecode.jobjectlife.internal.ManagedObjectInvocationHandler;
import com.googlecode.jobjectlife.internal.MethodCallConfiguration;

public class ManagedObjectFactory
{

    public static <T> T createManagedObject( Class<T> managedInterface, Class<? extends T> implementation,
                                             ILifecycleState states[] )
    {
        notNull( "managedInterface", managedInterface );
        isInterface( "managedInterface", managedInterface );
        notNull( "implementation", implementation );
        isConcreteClass( "implementation", implementation );
        notNull( "states", states );
        isDerived( "implementation", managedInterface, implementation );

        try
        {

            ObjectLifecycleManager<ILifecycleState> manager = new ObjectLifecycleManager<ILifecycleState>();

            initStates( states, manager );

            T implementationInstance = initImplementationObject( implementation, manager );

            if ( implementationInstance instanceof ILifecycleStateChangeListener )
            {
                manager.addLifecycleStateChangeListener( (ILifecycleStateChangeListener) implementationInstance );
            }

            Map<Method, MethodCallConfiguration> cfg = new HashMap<Method, MethodCallConfiguration>();

            for ( Method m : managedInterface.getMethods() )
            {
                MethodCallConfiguration mcfg = new MethodCallConfiguration();

                RequireState requireStateAnnotation = m.getAnnotation( RequireState.class );
                if ( requireStateAnnotation != null )
                {
                    String val = requireStateAnnotation.value();
                    ILifecycleState state = retrieveState( val, states );

                    if ( state == null )
                    {
                        throw new IllegalArgumentException( "method " + m.getName() + " has @RequireState"
                                        + " annotation with the state name '" + val
                                        + "' but no matching state has been found" );
                    }
                    mcfg.setRequiredState( state );
                }

                TransitionAfter transitionAfter = m.getAnnotation( TransitionAfter.class );

                if ( transitionAfter != null )
                {
                    String val = transitionAfter.value();
                    ILifecycleState state = retrieveState( val, states );
                    if ( state == null )
                    {
                        throw new IllegalArgumentException( "method " + m.getName() + " has @TransitionAfter"
                                        + " annotation with the state name '" + val
                                        + "' but no matching state has been found" );
                    }
                    mcfg.setTransitionAfterTarget( state );
                }
                if ( !mcfg.isEmpty() )
                {
                    cfg.put( m, mcfg );
                }
            }

            ManagedObjectInvocationHandler invocationHandler =
                new ManagedObjectInvocationHandler( implementationInstance, manager, cfg );
            T proxy =
                (T) Proxy.newProxyInstance( ManagedObjectFactory.class.getClassLoader(),
                                            new Class[] { managedInterface }, invocationHandler );

            return proxy;
        }
        catch ( Exception e )
        {
            throw new RuntimeException( "unable to create managed object proxy", e );
        }
    }

    private static ILifecycleState retrieveState( String val, ILifecycleState[] states )
    {
        ILifecycleState state = null;

        for ( ILifecycleState s : states )
        {
            if ( val.equals( s.name() ) )
            {
                state = s;
                break;
            }
        }
        return state;
    }

    private static void initStates( ILifecycleState[] states, ObjectLifecycleManager<ILifecycleState> manager )
    {

        for ( ILifecycleState s : states )
        {
            manager.appendLifecycleState( s );
        }

        manager.setInitialState( states[0] );
    }

    private static <T> T initImplementationObject( Class<? extends T> implementation,
                                                   ObjectLifecycleManager<ILifecycleState> manager )
        throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException
    {
        T implementationInstance = null;

        Throwable cause = null;
        try
        {
            implementationInstance = implementation.getConstructor( ObjectLifecycleManager.class ).newInstance( manager );
        }
        catch ( Exception e )
        {
            // try the default constructor
            try
            {
                implementationInstance = implementation.newInstance();
            }
            catch ( Exception e1 )
            {
                cause = e1;
            }
        }


        if ( implementationInstance == null )
        {
            throw new IllegalArgumentException( "implementation class has no default constructor "
                            + "and no constructor with a single ObjectLifecycleManager parameter" , cause);
        }
        return implementationInstance;
    }
}
